using System;
using System.Collections.Generic;

namespace Polymod
{
    internal class NewPropertyInvoker : IInvoker
    {
        private readonly string _name;
        private readonly Type _returnType;

        public NewPropertyInvoker(string name, Type returnType)
        {
            _name = name;
            _returnType = returnType;
        }

        public object GetValue(IModel target)
        {
            if (target == null) throw new System.ArgumentNullException("target");
            return GetPropertyBag(target)[_name];
        }

        public void SetValue(IModel target, object value)
        {
            if (target == null) throw new System.ArgumentNullException("target");
            GetPropertyBag(target)[_name] = value;
        }

        private IDictionary<string, object> GetPropertyBag(IModel target)
        {
            EnsureProperty(target);
            return (IDictionary<string, object>)target.ModelState[Constants.PropertyBag];
        }

        private void EnsureProperty(IModel target)
        {
            object propertyBag;
            if (!target.ModelState.TryGetValue(Constants.PropertyBag, out propertyBag))
            {
                lock (target.Synclock)
                {
                    if (!target.ModelState.TryGetValue(Constants.PropertyBag, out propertyBag))
                    {
                        propertyBag = new Dictionary<string, object>();
                        target.ModelState[Constants.PropertyBag] = propertyBag;
                    }
                }
            }

            var tmpPropertyBag = (Dictionary<string, object>)propertyBag;
            object propertyValue;
            if (!tmpPropertyBag.TryGetValue(_name, out propertyValue))
            {
                lock (propertyBag)
                {
                    if (!tmpPropertyBag.TryGetValue(_name, out propertyValue))
                    {
                        propertyValue = GetDefault(_returnType);
                        tmpPropertyBag[_name] = propertyValue;
                    }
                }
            }
        }

        private static object GetDefault(Type type)
        {
            if (type.IsValueType)
            {
                return Activator.CreateInstance(type);
            }
            return null;
        }

        /// <summary>
        /// Gets ReturnType
        /// </summary>
        public Type ReturnType
        {
            get { return _returnType; }
        }

        public bool IsReadonly
        {
            get { return false; }
        }

        /// <summary>
        /// Gets Name
        /// </summary>
        public string Name
        {
            get { return _name; }
        }

        public IInvoker Clone()
        {
            return new NewPropertyInvoker(_name, _returnType);
        }
    }
}
